package com.n9.util;

import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.UnsupportedEncodingException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * http请求工具类
 * @author ice
 */
public class Http {

	private static final Logger log = LoggerFactory.getLogger(Http.class);

	protected static final String COOKIE_STYLE = "%s=%s;";
	/**
	 * 最大总并发,很重要的参数
	 */
	static final int maxTotal = 10;
	/**
	 * 每路并发,很重要的参数
	 */
	static final int maxPerRoute = 10;

	/**
	 * ms毫秒,从池中获取链接超时时间
	 */
	static final int connectionRequestTimeout = 1000;
	/**
	 * ms毫秒,建立链接超时时间
	 */
	static final int connectTimeout = 20000;
	/**
	 * ms毫秒,读取超时时间
	 */
	static final int socketTimeout = 60000;

	private String encoding = "UTF-8";
	private boolean autoSaveCookie = true;
	private boolean autoSaveHeader = true;
	private boolean autoSendCookie = true;

	private CloseableHttpClient http = null;
	private Map<String, String> sendHeaders = new HashMap<String, String>(0);
	private Map<String, String> recvHeaders = new HashMap<>(0);
	private Map<String, String> cookies = new HashMap<String, String>(0);
	private int statusCode = 0;


	/**
	 * 调试模式
	 */
	private boolean debug = false;

	/**
	 */
	public Http() {
		http = init(null);
	}
	/**
	 *
	 * 使用代理
	 * @param proxyIp
	 * @param proxyPort
	 */
	public Http(String proxyIp, int proxyPort) {
		HttpHost proxy = new HttpHost(proxyIp, proxyPort, "http");
		http = init(proxy);
	}

	public int getStatusCode() {
		return statusCode;
	}
	public void close() {
		try {
			http.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public void setEncoding(String encoding) {
		this.encoding = encoding;
	}


	/**
	 * @param url 请求地址
	 * @param params 参数列表：name=value
	 * @return 结果
	 * @throws Exception
	 */
	public String Post(String url, List<String> params) {
		HttpPost post = new HttpPost(url);
		CloseableHttpResponse resp = null;
		try {
			setPostParam(params, post);
			resp = executeReuqest(post);
			HttpEntity entity = resp.getEntity();
			return EntityUtils.toString(entity, encoding);
		} catch (Exception e) {
			e.printStackTrace();
			log.error(e.getMessage());
		} finally {
			try {
				if (resp != null) {
					resp.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		return null;
	}
	/**
	 * 设置Post 提交的参数
	 * @param params		参数
	 * @param post			post请求对象
	 * @throws UnsupportedEncodingException
	 */
	private void setPostParam(List<String> params, HttpPost post)
			throws UnsupportedEncodingException {
		if (null != params){
			ArrayList<NameValuePair> list = new ArrayList<NameValuePair>();
			for (String s : params) {
				int i = s.indexOf("=");
				if (i <= 0) {
					continue;
				}
				list.add(new BasicNameValuePair(s.substring(0, i), s.substring(i + 1)));
			}
			post.setEntity(new UrlEncodedFormEntity(list, encoding));
		}
	}
	/**
	 * 发送Get请求
	 * @param url
	 * @return
	 * @throws IOException
	 */
	public String Get(String url) {
		HttpGet get = new HttpGet(url);
		CloseableHttpResponse response = null;
		try {
			response = executeReuqest(get);
			printRecvHeaders(response);
			HttpEntity entity = response.getEntity();
			return EntityUtils.toString(entity, encoding);
		} catch (Exception e) {
			e.printStackTrace();
			log.error(e.getMessage());
		} finally {
			try {
				if (response != null) {
					response.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}

	/**
	 * post文件数据
	 * @param url
	 * @param bytes
	 * @param fileName
	 * @return
	 */
	public String postFileBytes(String url, byte[] bytes, String fileName) {
		HttpPost post = new HttpPost(url);
		CloseableHttpResponse response = null;
		try {
			MultipartEntityBuilder builder = MultipartEntityBuilder.create();
			builder.addBinaryBody("file", bytes, ContentType.MULTIPART_FORM_DATA, fileName);
			builder.addTextBody("filename", fileName);
			post.setEntity(builder.build());
			response = executeReuqest(post);
			printRecvHeaders(response);
			HttpEntity entity = response.getEntity();
			return EntityUtils.toString(entity, encoding);
		} catch (Exception e) {
			e.printStackTrace();
			log.error(e.getMessage());
		} finally {
			try {
				if (response != null) {
					response.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}

	/**
	 * 请求byte数组
	 * @param url
	 * @return
	 */
	public byte[] GetBytes(String url) {
		HttpGet get = new HttpGet(url);
		CloseableHttpResponse response = null;
		try {
			response = executeReuqest(get);
			printRecvHeaders(response);
			HttpEntity entity = response.getEntity();
			return EntityUtils.toByteArray(entity);
		} catch (Exception e) {
			log.error(e.getMessage());
			e.printStackTrace();
		} finally {
			try {
				if (response != null) {
					response.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}


	/**
	 * @return
	 * 执行Http请求,自动设置请求头信息,请求完毕后,自动保存Cookie
	 * @param req
	 * @throws
	 */
	private CloseableHttpResponse executeReuqest(final HttpUriRequest req) throws IOException {
		putSendHeadersToHttp(req);
		printSendHeaders(req);

		CloseableHttpResponse response = http.execute(req);
		if (autoSaveCookie) {
			saveCookies(response);
		}
		if (autoSaveHeader) {
			saveRecvHeaders(response);
		}

		statusCode = response.getStatusLine().getStatusCode();
		return response;
	}

	/**
	 * 保存接受头
	 */
	private void saveRecvHeaders(CloseableHttpResponse response) {
		Header[] allHeaders = response.getAllHeaders();
		for (Header header : allHeaders) {
			recvHeaders.put(header.getName(), header.getValue());
		}
	}

	/**
	 * 设置HTTP 头信息
	 * @param req
	 */
	private void putSendHeadersToHttp(final HttpRequest req) {
		for (Entry<String, String> entry : sendHeaders.entrySet()) {
			req.addHeader(entry.getKey(), entry.getValue());
		}
		if (autoSendCookie) {
			cookieToHeader(req);
		}
	}
	/**
	 * 保存Cookie
	 * @param resp
	 */
	protected void saveCookies(final HttpResponse resp) {
		HeaderElementIterator it = new BasicHeaderElementIterator(resp.headerIterator("Set-Cookie"));

		while(it.hasNext()) {
			HeaderElement he = it.nextElement();
			if (he.getValue() != null) {
				cookies.put(he.getName(), he.getValue());
			}
		}
	}
	/**
	 * 设置Cookie
	 * @param req
	 */
	protected void cookieToHeader(final HttpRequest req) {
		String cookiesStr = getCookiesStr();
		if (!"".equals(cookiesStr)) {
			req.addHeader("Cookie", cookiesStr);
		}
	}
	/**
	 * 获取Cookies拼接后的值
	 * @return
	 */
	public String getCookiesStr() {
		StringBuffer cookie = new StringBuffer();
		for(Entry<String, String> entry : cookies.entrySet()) {
			cookie.append(String.format(COOKIE_STYLE, entry.getKey(), entry.getValue()));
		}

		return cookie.toString();
	}

	/**
	 * 获取cookies
	 * @return
	 */
	public Map<String, String> getCookies() {
		return cookies;
	}


	public void addCookie(String key, String value) {
		this.cookies.put(key, value);
	}

	/**
	 * 获取接受头信息
	 * @param name
	 * @return
	 */
	public String getRecvHeader(String name) {
		return this.recvHeaders.get(name);
	}

	/**
	 * 打印接收头
	 * @param resp
	 */
	protected void printRecvHeaders(final HttpResponse resp){
		if (debug) {
			log.info("");
			log.info("*********************************************");
			log.info("");
			Header[] hs = resp.getAllHeaders();
			for (Header h : hs) {
				log.info(h.getName() + ":" + h.getValue());
			}
			log.info("");
			log.info("*********************************************");
			log.info("");
		}
	}

	/**
	 * 打印接收头
	 * @param req
	 */
	protected void printSendHeaders(final HttpRequest req) {
		if (debug) {
			log.info("");
			log.info("*********************************************");
			log.info(" ");

			Header[] hs = req.getAllHeaders();
			for (Header h : hs) {
				log.info(h.getName() + ":" + h.getValue());
			}
			log.info("");
			log.info("*********************************************");
			log.info(" ");
		}
	}

	private CloseableHttpClient init(HttpHost proxy) {
		CloseableHttpClient newHttpclient = null;

		// 设置连接池
		ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();
		LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory.getSocketFactory();
		Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory> create().register("http", plainsf).register("https", sslsf).build();
		PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
		// 将最大连接数增加
		cm.setMaxTotal(maxTotal);
		// 将每个路由基础的连接增加
		cm.setDefaultMaxPerRoute(maxPerRoute);

		// 请求重试处理
		HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
			@Override
			public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
				// 如果已经重试了2次，就放弃
				if (executionCount >= 2) {
					return false;
				}
				// 如果服务器丢掉了连接，那么就重试
				if (exception instanceof NoHttpResponseException) {
					return true;
				}
				// 不要重试SSL握手异常
				if (exception instanceof SSLHandshakeException) {
					return false;
				}
				// 超时
				if (exception instanceof InterruptedIOException) {
					return false;
				}
				// 目标服务器不可达
				if (exception instanceof UnknownHostException) {
					return false;
				}
				// 连接被拒绝
				if (exception instanceof ConnectTimeoutException) {
					return false;
				}
				// SSL握手异常
				if (exception instanceof SSLException) {
					return false;
				}

				HttpClientContext clientContext = HttpClientContext.adapt(context);
				HttpRequest request = clientContext.getRequest();
				// 如果请求是幂等的，就再次尝试
				if (!(request instanceof HttpEntityEnclosingRequest)) {
					return true;
				}
				return false;
			}
		};
		// 配置请求的超时设置
		Builder configBuilder = RequestConfig.custom().setConnectionRequestTimeout(connectionRequestTimeout).setConnectTimeout(connectTimeout).setSocketTimeout(socketTimeout);
		if (proxy != null) {
			configBuilder.setProxy(proxy);
		}
		RequestConfig requestConfig = configBuilder.build();
		newHttpclient = HttpClients.custom().setConnectionManager(cm).setDefaultRequestConfig(requestConfig).setRetryHandler(httpRequestRetryHandler).build();
		return newHttpclient;
	}

	public void addHeader(String name, String value) {
		this.sendHeaders.put(name, value);
	}
}
